package com.tca.common.web.aspect;

import com.alibaba.fastjson.JSONObject;
import com.tca.common.core.bean.Result;
import com.tca.common.core.utils.ValidateUtils;
import com.tca.common.web.annotation.LogRecord;
import com.tca.common.web.annotation.ParameterConverter;
import com.tca.common.web.bean.LogRecordBean;
import com.tca.common.web.converter.InputParamConverter;
import com.tca.common.web.converter.OutputParamConverter;
import com.tca.common.web.handler.LogRecordListener;
import com.tca.common.web.handler.OperatorHandler;
import com.tca.common.web.utils.IpAddressUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author zhoua
 * @date 2022/1/12
 */
@Aspect
@Slf4j
public class LogRecordAspect {
    
    /**
     * 日志处理器
     */
    @Autowired(required = false)
    @Lazy
    private List<LogRecordListener> logRecordListenerList;
    
    /**
     * 操作人员处理器
     */
    @Autowired(required = false)
    private OperatorHandler operatorHandler;
    
    /**
     * 解析参数名称, 前提 -- 编译的时候加上 javac -parameters, 配置相关maven插件
     */
    @Autowired
    private ParameterNameDiscoverer parameterNameDiscoverer;
    
    /**
     * 参数转换器
     */
    private Map<Class<? extends InputParamConverter>, InputParamConverter> inputParamConverterMap = new ConcurrentHashMap<>();
    
    /**
     * 参数转换器
     */
    private Map<Class<OutputParamConverter>, OutputParamConverter> outputParamConverterMap = new ConcurrentHashMap<>();
    
    
    @Pointcut(value = "@annotation(com.tca.common.web.annotation.LogRecord)")
    public void pointCut() {}
    
    @Around(value = "pointCut()")
    public Object controllerLog(ProceedingJoinPoint joinPoint) throws Throwable {
        return doAround(joinPoint);
    }
    
    /**
     * 日志切面处理
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    public Object doAround(ProceedingJoinPoint joinPoint) {
        LocalDateTime now = LocalDateTime.now();
        long startTimestamp = System.currentTimeMillis();
        
        // 1.解决异步线程取不到request的问题
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (ValidateUtils.isNotEmpty(requestAttributes)) {
            RequestContextHolder.setRequestAttributes(requestAttributes, true);
        }
        HttpServletRequest request = requestAttributes.getRequest();
        
        
        // 2.获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        // 3.获取注解
        LogRecord recordLog = method.getAnnotation(LogRecord.class);
        
        // 4.数据封装
        LogRecordBean logRecordBean = LogRecordBean.builder()
                .ip(getIp(request))
                .uri(getUri(request))
                .tenantId(getTenantId(request))
                .departmentId(getDepartmentId(request))
                .module(recordLog.module())
                .operation(recordLog.operation())
                .operationTime(String.valueOf(System.currentTimeMillis()))
                .operationTimestamp(startTimestamp)
                .method(method.getName())
                .methodType(getMethodType(method))
                .createTime(now)
                .updateTime(now)
				.accountId(getAccountId(request))
                .accountName(getUsername(request))
                .build();
        
        // 5.方法调用
        Result<Object> result;
        try {
            result = (Result<Object>)joinPoint.proceed();
        } catch (Throwable e) {
            log.error("操作失败", e);
            result = Result.failure();
        }
        long timeConsume = System.currentTimeMillis() - startTimestamp;
        logRecordBean.setCode(result.getCode());
        logRecordBean.setMessage(result.getMsg());
        logRecordBean.setTimeConsume(timeConsume);
        // 入参处理
        logRecordBean.setInputParam(getInputParam(method, joinPoint.getArgs()));
        // 出参处理
        logRecordBean.setOutputParam(getOutputParam(result, recordLog.outputParamConverters()));
        // 日志存储模式
		logRecordBean.setStorageType(recordLog.storageType());

        // 6.发布日志事件
        publishLogRecordEvent(logRecordBean);
        
        return result;
        
        
    }
    
    
    
    
    /**
     * 发布日志事件
     * @param logRecordBean
     */
    private void publishLogRecordEvent(LogRecordBean logRecordBean) {
        if (ValidateUtils.isEmpty(logRecordListenerList)) {
            return;
        }
        logRecordListenerList.forEach(logRecordListener -> logRecordListener.onLogRecordEvent(logRecordBean));
    }
    
    /**
     * 解析响应参数
     * @param result
     * @param outputParamConverters
     * @return
     */
    private String getOutputParam(Result<Object> result, Class<OutputParamConverter>[] outputParamConverters) {
        try {
            if (outputParamConverters == null || outputParamConverters.length == 0) {
                return JSONObject.toJSONString(result);
            }
            for (int i = 0; i < outputParamConverters.length; i++) {
                OutputParamConverter outputParamConverter = outputParamConverterMap.get(outputParamConverters[i]);
                if (ValidateUtils.isEmpty(outputParamConverter)) {
                    try {
                        outputParamConverter = outputParamConverters[i].getDeclaredConstructor().newInstance();
                        outputParamConverterMap.put(outputParamConverters[i], outputParamConverter);
                    } catch (Exception e) {
                        log.error("实例化 paramConverter 出错", e);
                        continue;
                    }
                }
                result = outputParamConverter.apply(result);
            }
            return JSONObject.toJSONString(result);
        } catch (Exception e) {
            log.error("日志入参解析失败: ", e);
            return "";
        }
        
    }
    
    /**
     * 解析请求参数
     * 不处理HttpServletRequest
     * @param args
     * @return
     */
    private String getInputParam(Method method, Object[] args) {
        try {
            if (args == null || args.length == 0) {
                return null;
            }
            
			HashMap<String, Object> result = new HashMap<>();
            
            // 解析方法形参names
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
            
            // 解析方法形参type
            Parameter[] parameters = method.getParameters();
            
            // 解析方法实参
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if(arg == null ){
                	continue;
				}
                // 判断当前参数是否有@ParameterConverter注解
                Parameter parameter = parameters[i];
                // 参数是 HttpServletRequest, 跳过
                if (HttpServletRequest.class.isAssignableFrom(parameter.getType())) {
                    continue;
                }
                
                ParameterConverter parameterConverter = parameter.getAnnotation(ParameterConverter.class);
                if (ValidateUtils.isNotEmpty(parameterConverter)) {
                    Class<? extends InputParamConverter>[] inputParamConverters = parameterConverter.value();
                    if (inputParamConverters != null && inputParamConverters.length > 0) {
                        for (int j = 0; j < inputParamConverters.length; j++) {
                            InputParamConverter inputParamConverter = inputParamConverterMap.get(inputParamConverters[j]);
                            if (ValidateUtils.isEmpty(inputParamConverter)) {
                                try {
                                    inputParamConverter = inputParamConverters[j].getDeclaredConstructor().newInstance();
                                    inputParamConverterMap.put(inputParamConverters[j], inputParamConverter);
                                } catch (Exception e) {
                                    log.error("实例化 paramConverter 出错", e);
                                    continue;
                                }
                            }
                            arg = inputParamConverter.apply(arg);
                        }
                    }
                }
                // 判断参数类型
                Class<?> type = parameter.getType();
                if (arg instanceof String || arg instanceof Number) {
                    result.put(parameterNames[i], arg);
                } else if (type.isArray() || arg instanceof Collection) {
                    result.put(parameterNames[i], JSONObject.toJSONString(arg));
                } else {
                    result.put(parameterNames[i], JSONObject.toJSONString(arg));
                }
            }
            
            return JSONObject.toJSONString(result);
        } catch (Exception e) {
            log.error("日志入参解析失败: ", e);
            return "";
        }
        
    }
    
    /**
     * 获取请求方法
     * @param method
     * @return
     */
    private String getMethodType(Method method) {
        if (ValidateUtils.isEmpty(method)) {
            return "";
        }
        // 获取RequestMapping相关属性, 使用 AnnotatedElementUtils#findMergedAnnotation 方法, 可以获取到GetMapping、PostMapping等
        RequestMapping methodMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        if (ValidateUtils.isEmpty(methodMapping)) {
            return "";
        }
        RequestMethod[] requestMethod = methodMapping.method();
        if (requestMethod == null || requestMethod.length == 0) {
            return "";
        }
        return requestMethod[0].name();
        
    }
    
    /**
     * 获取用户名
     * @return
     */
    private String getUsername(HttpServletRequest httpServletRequest) {
        return ValidateUtils.isEmpty(operatorHandler) ? "" : operatorHandler.getAccountName(httpServletRequest);
    }

	/**
	 * 获取账号
	 * @return
	 */
	private Long getAccountId(HttpServletRequest httpServletRequest) {
		return ValidateUtils.isEmpty(operatorHandler) ? -1 : operatorHandler.getAccountId(httpServletRequest);
	}
    
    /**
     * 获取ip
     * @param httpServletRequest
     * @return
     */
    private String getIp( HttpServletRequest httpServletRequest) {
        return ValidateUtils.isEmpty(httpServletRequest)? "":
                IpAddressUtil.getIpAddress(httpServletRequest);
    }
    
    /**
     * 获取请求url
     * @param httpServletRequest
     * @return
     */
    private String getUri(HttpServletRequest httpServletRequest) {
        return ValidateUtils.isEmpty(httpServletRequest)? "": httpServletRequest.getRequestURI();
    }
    
    /**
     * 获取租户id
     * @return
     */
    private Long getTenantId(HttpServletRequest httpServletRequest) {
        return ValidateUtils.isEmpty(operatorHandler)? null: operatorHandler.getTenantId(httpServletRequest);
    }
    
    /**
     * 获取部门id
     * @return
     */
    private Long getDepartmentId(HttpServletRequest httpServletRequest) {
        return ValidateUtils.isEmpty(operatorHandler)? null: operatorHandler.getDepartmentId(httpServletRequest);
    }
}
